package net.danielthompson.danray.shapes;

import net.danielthompson.danray.shading.Material;
import net.danielthompson.danray.states.IntersectionState;
import net.danielthompson.danray.structures.BoundingBox;
import net.danielthompson.danray.structures.Point;
import net.danielthompson.danray.structures.Vector;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by daniel on 2/17/14.
 */
public class Box implements Drawable {

   private Material _material;
   private Point _point1;
   private Point _point2;

   private final ImplicitPlane _x1;
   private final ImplicitPlane _x2;
   private final ImplicitPlane _z1;
   private final ImplicitPlane _z2;
   private final ImplicitPlane _y1;
   private final ImplicitPlane _y2;

   private final List<ImplicitPlane> _planes;

   public Box (Point p1, Point p2, Material material) {
      _point1 = p1;
      _point2 = p2;
      _material = material;

      _x1 = new ImplicitPlane(_point1, new Vector(_point1, new Point(-1, 0, 0)), _material);
      _x2 = new ImplicitPlane(_point2, new Vector(_point2, new Point(1, 0, 0)), _material);

      _z1 = new ImplicitPlane(_point1, new Vector(_point1, new Point(0, 0, -1)), _material);
      _z2 = new ImplicitPlane(_point2, new Vector(_point2, new Point(0, 0, 1)), _material);

      _y1 = new ImplicitPlane(_point1, new Vector(_point1, new Point(0, -1, 0)), _material);
      _y2 = new ImplicitPlane(_point2, new Vector(_point2, new Point(0, 1, 0)), _material);

      _planes = new ArrayList<ImplicitPlane>();
      _planes.add(_x1);
      _planes.add(_x2);
      _planes.add(_y1);
      _planes.add(_y2);
      _planes.add(_z1);
      _planes.add(_z2);
   }

   @Override
   public Material GetMaterial() {
      return _material;
   }

   @Override
   public IntersectionState GetHitInfo(Vector vector) {

      double tFar = Double.MAX_VALUE;
      double tNear = 0;

      IntersectionState noHit = new IntersectionState();
      noHit.Drawable = this;
      noHit.Hits = false;

      IntersectionState intersectionState = new IntersectionState();

      intersectionState.Drawable = this;
      intersectionState.Hits = false;

      for (int i = 0; i < _planes.size(); i+= 2) {
         ImplicitPlane p1 = _planes.get(i);
         ImplicitPlane p2 = _planes.get(i + 1);

         IntersectionState near = p1.GetHitInfo(vector);
         IntersectionState far = p2.GetHitInfo(vector);

         if (near.T > far.T)
         {
            IntersectionState temp = near;
            near = far;
            far = temp;
         }

         if (!near.Hits && !far.Hits) {
            if (!Inside(p1, p2, vector.Origin)) {
               return noHit;
            }
            else if (near.T < 0 ) {
               continue;
            }
         }

         if (tNear > far.T || tFar < near.T)
            return noHit;

         if (far.T < tFar)
            tFar = far.T;
         if (near.T > tNear)
            tNear = near.T;

         if (tNear > 0 && tNear < tFar) {
            intersectionState.Hits = true;
            intersectionState.T = tNear;
            intersectionState.IntersectionPoint = near.IntersectionPoint;
            intersectionState.Normal = near.Normal;
         }
      }

      if (intersectionState.IntersectionPoint == null) {
         return noHit;
      }

      return intersectionState;
   }

   private boolean Inside(ImplicitPlane p1, ImplicitPlane p2, Point p) {
      Point normalDirection = p1.GetNormal().Direction;
      Point connectingDirection = Point.Minus(p, p1.GetNormal().Origin);
      double dotproduct = normalDirection.Dot(connectingDirection);

      if (dotproduct > 0) {
         return false;
      }

      normalDirection = p2.GetNormal().Direction;
      connectingDirection = Point.Minus(p, p2.GetNormal().Origin);
      dotproduct = normalDirection.Dot(connectingDirection);

      if (dotproduct > 0) {
         return false;
      }

      return true;
   }

   @Override
   public double BRDF(IntersectionState state, Vector cameraRay, Vector lightRay) {
      return 0;
   }

   @Override
   public double GetVolume() {
      return 0;
   }

   @Override
   public BoundingBox GetBoundingBox() {
      return null;
   }
}
